/////////////////////////////////////////////////////////////////////////
//
// Amuse Engine SDK - resource
// Copyright( c) 2014.  All Rights Reserved
//
// File:		AEGPUProgramOGL.cpp
// Author:		Gianluca Belardelli
// Date:		12/12/2014
//
/////////////////////////////////////////////////////////////////////////
#include <AEEngine.h>
#include "graphics\shaders\AEGPUProgramOGL.h"

const char *GL_type_to_string( GLenum glType ) 
{
	switch( glType ) 
	{
		case GL_BOOL: return "bool";
		case GL_INT: return "int";
		case GL_FLOAT: return "float";
		case GL_FLOAT_VEC2: return "vec2";
		case GL_FLOAT_VEC3: return "vec3";
		case GL_FLOAT_VEC4: return "vec4";
		case GL_FLOAT_MAT2: return "mat2";
		case GL_FLOAT_MAT3: return "mat3";
		case GL_FLOAT_MAT4: return "mat4";
		case GL_SAMPLER_2D: return "sampler2D";
		case GL_SAMPLER_CUBE: return "samplerCube";

	#ifdef AE_OPENGL
		case GL_SAMPLER_3D: return "sampler3D";
		case GL_SAMPLER_2D_SHADOW: return "sampler2DShadow";
	#endif
		
		default: break;
	}
	return "other";
}

AEGPUProgramOGL::AEGPUProgramOGL( void )
{
	m_uiShaderHandle = 0;
}

AEGPUProgramOGL::~AEGPUProgramOGL( void )
{
}
	
AEUINT32 AEGPUProgramOGL::LoadAndCompile( AEShaderStage eShaderStage, const AEShaderProgramResource *lpShaderProgramResource, const char *lpFileName )
{
	GLuint uiShaderID;
	GLenum eShaderType;
	GLint nMaxUniforms = -1;

	if( m_uiShaderHandle )
		return AE_NO_ERROR;

	switch( eShaderStage )
	{
		case AESHADERSTAGE_VERTEXSHADER: 
			eShaderType = GL_VERTEX_SHADER; 
			//glGetIntegerv( GL_MAX_VERTEX_UNIFORM_COMPONENTS, &nMaxUniforms );
			//if( nMaxUniforms == -1 )
			//	nMaxUniforms = 1024;
			break;
		case AESHADERSTAGE_PIXELSHADER:
			eShaderType = GL_FRAGMENT_SHADER; 
			//glGetIntegerv( GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &nMaxUniforms );
			break;
		//case AESHADERSTAGE_SKINNINGSHADER: eShaderType = GL_VERTEX_SHADER; break;
#ifdef AE_OPENGL
		case AESHADERSTAGE_GEOMETRYSHADER: 
			eShaderType = GL_GEOMETRY_SHADER;
			glGetIntegerv( GL_MAX_GEOMETRY_UNIFORM_COMPONENTS, &nMaxUniforms );
			break;
		case AESHADERSTAGE_DOMAINSHADER: eShaderType = GL_TESS_EVALUATION_SHADER; break;
		case AESHADERSTAGE_HULLSHADER: eShaderType =  GL_TESS_CONTROL_SHADER; break;
		case AESHADERSTAGE_COMPUTESHADER: eShaderType = GL_COMPUTE_SHADER; break;
#endif
		default:
			return AE_INVALIDSHADERSTAGE;
	};

	AEFileInStreamI *lpStream = AEFileManager::GetInstance()->Open( lpFileName );
	if( !lpStream )
		return AE_SHADERNOTFOUND;

	AEINT32 nSize = lpStream->GetSize();
	AEUINT8 *lpBuffer = new AEUINT8[nSize+1];

	lpStream->Read( lpBuffer, nSize );

	lpBuffer[nSize] = '\0';
	lpStream->Close();

	m_uiShaderHandle = glCreateShader( eShaderType );

	glShaderSource( m_uiShaderHandle, 1, (const GLchar**)&lpBuffer, (GLint *)&nSize );

	glCompileShader( m_uiShaderHandle );

	GLint nCompileResult;
	glGetShaderiv( m_uiShaderHandle, GL_COMPILE_STATUS, &nCompileResult );

	delete [] lpBuffer;

	if( !nCompileResult )
	{
		GLint nMaxLength = 0;
		glGetShaderiv( m_uiShaderHandle, GL_INFO_LOG_LENGTH, &nMaxLength );
 
		// The maxLength includes the NULL character
		char *lpErrorLog = new char[nMaxLength];
		glGetShaderInfoLog( m_uiShaderHandle, nMaxLength, &nMaxLength, lpErrorLog );
		printf("ERROR: %s\n", lpErrorLog);
		glDeleteShader( m_uiShaderHandle );
		m_uiShaderHandle = 0;
		return AE_SHADERCOMPILEERROR;
	}

	const GLuint glProgram = glCreateProgram();

	glAttachShader( glProgram, m_uiShaderHandle );
	glLinkProgram( glProgram );
	glGetProgramiv( glProgram, GL_LINK_STATUS, &nCompileResult );

	if( nCompileResult )
	{
		char szName[64];
		AEINT32 nMaxLength = 64;
		AEINT32 nActualLength = 0;
		AEINT32 nCount = 0;
		AEINT32 nLocation = -1;
		GLenum eType;

		glGetProgramiv( glProgram, GL_ACTIVE_ATTRIBUTES, (GLint *)&nCount );
		
		for( int i = 0; i < nCount; i++ )
		{
			glGetActiveAttrib( glProgram, i, nMaxLength, (GLint *)&nActualLength, (GLint *)&nSize, &eType, szName );

			if( nSize > 1 )
			{
				for( int j = 0; j < nSize; j++ )
				{
					char szLongName[64];
					sprintf( szLongName, "%s[%i]", szName, j );
					nLocation = glGetAttribLocation( glProgram, szLongName );

					#ifdef AEDEBUG
						printf ("  %i) ATTRIB -> type:%s name:%s location:%i\n", i, GL_type_to_string( eType ), szLongName, nLocation );
					#endif
				}
			}
			else
			{
				nLocation = glGetAttribLocation( glProgram, szName );

				#ifdef AEDEBUG
					printf ("  %i) ATTRIB -> type:%s name:%s location:%d\n", i, GL_type_to_string( eType ), szName, nLocation );
				#endif
			}

			//AERegisterToUniformMappingEntry map;
			//map.nRegister = nLocation;
			//map.uiUniformType =
		}

		glGetProgramiv( glProgram, GL_ACTIVE_UNIFORMS, (GLint *)&nCount );

		for( int i = 0; i < nCount; i++ )
		{
			glGetActiveUniform( glProgram, i, nMaxLength, (GLint *)&nActualLength, (GLint *)&nSize, &eType, szName );
			
			if( nSize > 1 )
			{
				for( int j = 0; j < nSize; j++ )
				{
					char szLongName[64];
					sprintf( szLongName, "%s[%i]", szName, j );
					nLocation = glGetUniformLocation( glProgram, szLongName );
					if( nLocation != GL_INVALID_OPERATION && nLocation != GL_INVALID_VALUE )
					{
						#ifdef AEDEBUG
							printf ("  %i) type:%s name:%s location:%d\n", i, GL_type_to_string( eType ), szLongName, nLocation );
						#endif
					}
				}
			}
			else
			{
				nLocation = glGetUniformLocation( glProgram, szName );
				if( nLocation != GL_INVALID_OPERATION && nLocation != GL_INVALID_VALUE )
				{
					#ifdef AEDEBUG
						printf ("  %i) UNIFORM -> type:%s name:%s location:%d\n", i, GL_type_to_string( eType ), szName, nLocation );
					#endif
				}
			}
		}
	}
	else
	{
		GLint nMaxLength = 0;
		glGetProgramiv( glProgram, GL_INFO_LOG_LENGTH, &nMaxLength );
 
		// The maxLength includes the NULL character
		char *lpErrorLog = new char[nMaxLength];
		glGetProgramInfoLog( glProgram, nMaxLength, &nMaxLength, lpErrorLog );

		glDeleteProgram( glProgram );
		return AE_SHADERLINKERROR;
	}

	glDeleteProgram( glProgram );

	//glBindAttribLocation( glProgram, 0, "in_Position" );
	//glUseProgram(glProgram);

	return AE_NO_ERROR;
}



